/*
 * contrib/btree_gin/btree_gin.c
 */
#include "postgres.h"

#include <limits.h>

#include "access/stratnum.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
#include "utils/cash.h"
#include "utils/date.h"
#include "utils/float.h"
#include "utils/inet.h"
#include "utils/numeric.h"
#include "utils/timestamp.h"
#include "utils/uuid.h"
#include "utils/varbit.h"

PG_MODULE_MAGIC;

typedef struct QueryInfo
{
	StrategyNumber strategy;
	Datum		datum;
	bool		is_varlena;
	Datum		(*typecmp) (FunctionCallInfo);
} QueryInfo;

/*** GIN 支持的函数，适用于所有数据类型 ***/

static Datum fc_gin_btree_extract_value(FunctionCallInfo fcinfo, bool fc_is_varlena)
{
	Datum		fc_datum = PG_GETARG_DATUM(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	Datum	   *fc_entries = (Datum *) palloc(sizeof(Datum));

	if (fc_is_varlena)
		fc_datum = PointerGetDatum(PG_DETOAST_DATUM(fc_datum));
	fc_entries[0] = fc_datum;
	*fc_nentries = 1;

	PG_RETURN_POINTER(fc_entries);
}

/*
 * 对于 BTGreaterEqualStrategyNumber、BTGreaterStrategyNumber 和
 * BTEqualStrategyNumber，我们希望从提供的查询数据项开始索引扫描，
 * 并向前工作。对于 BTLessStrategyNumber 和 BTLessEqualStrategyNumber，
 * 我们需要从最左边的键开始，并向前工作，直到提供的查询数据项（必须
 * 在 QueryInfo 结构中传递）。
 */
static Datum fc_gin_btree_extract_query(FunctionCallInfo fcinfo,
						bool fc_is_varlena,
						Datum (*fc_leftmostvalue) (void),
						Datum (*fc_typecmp) (FunctionCallInfo))
{
	Datum		fc_datum = PG_GETARG_DATUM(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(2);
	bool	  **fc_partialmatch = (bool **) PG_GETARG_POINTER(3);
	Pointer   **fc_extra_data = (Pointer **) PG_GETARG_POINTER(4);
	Datum	   *fc_entries = (Datum *) palloc(sizeof(Datum));
	QueryInfo  *fc_data = (QueryInfo *) palloc(sizeof(QueryInfo));
	bool	   *fc_ptr_partialmatch;

	*fc_nentries = 1;
	fc_ptr_partialmatch = *fc_partialmatch = (bool *) palloc(sizeof(bool));
	*fc_ptr_partialmatch = false;
	if (fc_is_varlena)
		fc_datum = PointerGetDatum(PG_DETOAST_DATUM(fc_datum));
	fc_data->strategy = fc_strategy;
	fc_data->datum = fc_datum;
	fc_data->is_varlena = fc_is_varlena;
	fc_data->typecmp = fc_typecmp;
	*fc_extra_data = (Pointer *) palloc(sizeof(Pointer));
	**fc_extra_data = (Pointer) fc_data;

	switch (fc_strategy)
	{
		case BTLessStrategyNumber:
		case BTLessEqualStrategyNumber:
			fc_entries[0] = fc_leftmostvalue();
			*fc_ptr_partialmatch = true;
			break;
		case BTGreaterEqualStrategyNumber:
		case BTGreaterStrategyNumber:
			*fc_ptr_partialmatch = true;
			/************************************************************
	 * 通过其键获取准备好的计划描述符
	 ************************************************************/
		case BTEqualStrategyNumber:
			fc_entries[0] = fc_datum;
			break;
		default:
			elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
	}

	PG_RETURN_POINTER(fc_entries);
}

/*
 * Datum a 是来自 extract_query 方法的值，对于 BTLess*
 * 策略，它是最左边的值。因此，使用来自 QueryInfo 的原始数据
 * 来决定是否停止扫描。Datum b 始终来自索引。
 */
static Datum fc_gin_btree_compare_prefix(FunctionCallInfo fcinfo)
{
	Datum		fc_a = PG_GETARG_DATUM(0);
	Datum		fc_b = PG_GETARG_DATUM(1);
	QueryInfo  *fc_data = (QueryInfo *) PG_GETARG_POINTER(3);
	int32		fc_res,
				fc_cmp;

	fc_cmp = DatumGetInt32(CallerFInfoFunctionCall2(fc_data->typecmp,
												 fcinfo->flinfo,
												 PG_GET_COLLATION(),
												 (fc_data->strategy == BTLessStrategyNumber ||
												  fc_data->strategy == BTLessEqualStrategyNumber)
												 ? fc_data->datum : fc_a,
												 fc_b));

	switch (fc_data->strategy)
	{
		case BTLessStrategyNumber:
			/* 如果原始数据 > 索引数据则返回匹配 */
			if (fc_cmp > 0)
				fc_res = 0;
			else
				fc_res = 1;
			break;
		case BTLessEqualStrategyNumber:
			/* 相同，除了相等 */
			if (fc_cmp >= 0)
				fc_res = 0;
			else
				fc_res = 1;
			break;
		case BTEqualStrategyNumber:
			if (fc_cmp != 0)
				fc_res = 1;
			else
				fc_res = 0;
			break;
		case BTGreaterEqualStrategyNumber:
			/* 如果原始数据 <= 索引数据则返回匹配 */
			if (fc_cmp <= 0)
				fc_res = 0;
			else
				fc_res = 1;
			break;
		case BTGreaterStrategyNumber:
			/* 如果原始数据 <= 索引数据则返回匹配 */
			/* 如果原始数据 == 索引数据则继续扫描 */
			if (fc_cmp < 0)
				fc_res = 0;
			else if (fc_cmp == 0)
				fc_res = -1;
			else
				fc_res = 1;
			break;
		default:
			elog(ERROR, "unrecognized strategy number: %d",
				 fc_data->strategy);
			fc_res = 0;
	}

	PG_RETURN_INT32(fc_res);
}

PG_FUNCTION_INFO_V1(gin_btree_consistent);
Datum gin_btree_consistent(PG_FUNCTION_ARGS)
{
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(5);

	*fc_recheck = false;
	PG_RETURN_BOOL(true);
}

/*** GIN_SUPPORT 宏定义了数据类型特定的函数 ***/

#define GIN_SUPPORT(type, is_varlena, leftmostvalue, typecmp)				\
PG_FUNCTION_INFO_V1(gin_extract_value_##type);								\
Datum																		\
gin_extract_value_##type(PG_FUNCTION_ARGS)									\
{																			\
	return fc_gin_btree_extract_value(fcinfo, is_varlena);						\
}	\
PG_FUNCTION_INFO_V1(gin_extract_query_##type);								\
Datum																		\
gin_extract_query_##type(PG_FUNCTION_ARGS)									\
{																			\
	return fc_gin_btree_extract_query(fcinfo,									\
								   is_varlena, leftmostvalue, typecmp);		\
}	\
PG_FUNCTION_INFO_V1(gin_compare_prefix_##type);								\
Datum																		\
gin_compare_prefix_##type(PG_FUNCTION_ARGS)									\
{																			\
	return fc_gin_btree_compare_prefix(fcinfo);								\
}


/*** 数据类型规范 ***/

static Datum fc_leftmostvalue_int2(void)
{
	return Int16GetDatum(SHRT_MIN);
}

GIN_SUPPORT(int2, false, fc_leftmostvalue_int2, btint2cmp)

static Datum fc_leftmostvalue_int4(void)
{
	return Int32GetDatum(INT_MIN);
}

GIN_SUPPORT(int4, false, fc_leftmostvalue_int4, btint4cmp)

static Datum fc_leftmostvalue_int8(void)
{
	return Int64GetDatum(PG_INT64_MIN);
}

GIN_SUPPORT(int8, false, fc_leftmostvalue_int8, btint8cmp)

static Datum fc_leftmostvalue_float4(void)
{
	return Float4GetDatum(-get_float4_infinity());
}

GIN_SUPPORT(float4, false, fc_leftmostvalue_float4, btfloat4cmp)

static Datum fc_leftmostvalue_float8(void)
{
	return Float8GetDatum(-get_float8_infinity());
}

GIN_SUPPORT(float8, false, fc_leftmostvalue_float8, btfloat8cmp)

static Datum fc_leftmostvalue_money(void)
{
	return Int64GetDatum(PG_INT64_MIN);
}

GIN_SUPPORT(money, false, fc_leftmostvalue_money, cash_cmp)

static Datum fc_leftmostvalue_oid(void)
{
	return ObjectIdGetDatum(0);
}

GIN_SUPPORT(oid, false, fc_leftmostvalue_oid, btoidcmp)

static Datum fc_leftmostvalue_timestamp(void)
{
	return TimestampGetDatum(DT_NOBEGIN);
}

GIN_SUPPORT(timestamp, false, fc_leftmostvalue_timestamp, timestamp_cmp)

GIN_SUPPORT(timestamptz, false, fc_leftmostvalue_timestamp, timestamp_cmp)

static Datum fc_leftmostvalue_time(void)
{
	return TimeADTGetDatum(0);
}

GIN_SUPPORT(time, false, fc_leftmostvalue_time, time_cmp)

static Datum fc_leftmostvalue_timetz(void)
{
	TimeTzADT  *fc_v = palloc(sizeof(TimeTzADT));

	fc_v->time = 0;
	fc_v->zone = -24 * 3600;		/* XXX 这是真的吗？ */

	return TimeTzADTPGetDatum(fc_v);
}

GIN_SUPPORT(timetz, false, fc_leftmostvalue_timetz, timetz_cmp)

static Datum fc_leftmostvalue_date(void)
{
	return DateADTGetDatum(DATEVAL_NOBEGIN);
}

GIN_SUPPORT(date, false, fc_leftmostvalue_date, date_cmp)

static Datum fc_leftmostvalue_interval(void)
{
	Interval   *fc_v = palloc(sizeof(Interval));

	fc_v->time = PG_INT64_MIN;
	fc_v->day = PG_INT32_MIN;
	fc_v->month = PG_INT32_MIN;
	return IntervalPGetDatum(fc_v);
}

GIN_SUPPORT(interval, false, fc_leftmostvalue_interval, interval_cmp)

static Datum fc_leftmostvalue_macaddr(void)
{
	macaddr    *fc_v = palloc0(sizeof(macaddr));

	return MacaddrPGetDatum(fc_v);
}

GIN_SUPPORT(macaddr, false, fc_leftmostvalue_macaddr, macaddr_cmp)

static Datum fc_leftmostvalue_macaddr8(void)
{
	macaddr8   *fc_v = palloc0(sizeof(macaddr8));

	return Macaddr8PGetDatum(fc_v);
}

GIN_SUPPORT(macaddr8, false, fc_leftmostvalue_macaddr8, macaddr8_cmp)

static Datum fc_leftmostvalue_inet(void)
{
	return DirectFunctionCall1(inet_in, CStringGetDatum("0.0.0.0/0"));
}

GIN_SUPPORT(inet, true, fc_leftmostvalue_inet, network_cmp)

GIN_SUPPORT(cidr, true, fc_leftmostvalue_inet, network_cmp)

static Datum fc_leftmostvalue_text(void)
{
	return PointerGetDatum(cstring_to_text_with_len("", 0));
}

GIN_SUPPORT(text, true, fc_leftmostvalue_text, bttextcmp)

GIN_SUPPORT(bpchar, true, fc_leftmostvalue_text, bpcharcmp)

static Datum fc_leftmostvalue_char(void)
{
	return CharGetDatum(0);
}

GIN_SUPPORT(char, false, fc_leftmostvalue_char, btcharcmp)

GIN_SUPPORT(bytea, true, fc_leftmostvalue_text, byteacmp)

static Datum fc_leftmostvalue_bit(void)
{
	return DirectFunctionCall3(bit_in,
							   CStringGetDatum(""),
							   ObjectIdGetDatum(0),
							   Int32GetDatum(-1));
}

GIN_SUPPORT(bit, true, fc_leftmostvalue_bit, bitcmp)

static Datum fc_leftmostvalue_varbit(void)
{
	return DirectFunctionCall3(varbit_in,
							   CStringGetDatum(""),
							   ObjectIdGetDatum(0),
							   Int32GetDatum(-1));
}

GIN_SUPPORT(varbit, true, fc_leftmostvalue_varbit, bitcmp)

/*
 * 数值类型没有真正的最左边值，因此我们使用 PointerGetDatum(NULL)
 * (*不是* SQL NULL) 来表示它。我们能够做到这一点，因为
 * 我们的 leftmostvalue 函数返回的值永远不会存储在
 * 索引中，也不会传递给除我们的比较和前缀比较
 * 函数之外的任何东西。其他按引用传递的类型也可以使用相同的技巧。
 */

#define NUMERIC_IS_LEFTMOST(x)	((x) == NULL)

PG_FUNCTION_INFO_V1(gin_numeric_cmp);

Datum gin_numeric_cmp(PG_FUNCTION_ARGS)
{
	Numeric		fc_a = (Numeric) PG_GETARG_POINTER(0);
	Numeric		fc_b = (Numeric) PG_GETARG_POINTER(1);
	int			fc_res = 0;

	if (NUMERIC_IS_LEFTMOST(fc_a))
	{
		fc_res = (NUMERIC_IS_LEFTMOST(fc_b)) ? 0 : -1;
	}
	else if (NUMERIC_IS_LEFTMOST(fc_b))
	{
		fc_res = 1;
	}
	else
	{
		fc_res = DatumGetInt32(DirectFunctionCall2(numeric_cmp,
												NumericGetDatum(fc_a),
												NumericGetDatum(fc_b)));
	}

	PG_RETURN_INT32(fc_res);
}

static Datum fc_leftmostvalue_numeric(void)
{
	return PointerGetDatum(NULL);
}

GIN_SUPPORT(numeric, true, fc_leftmostvalue_numeric, gin_numeric_cmp)

/*
 * 对于枚举使用类似于数值的技巧，因为我们实际上不知道
 * 任何枚举的最左边值，除非知道具体类型，所以我们使用无效的最左边值
 * InvalidOid。
 *
 * 注意，我们在这里使用 CallerFInfoFunctionCall2，以便 enum_cmp
 * 获得有效的 fn_extra 来处理。与大多数其他类型比较
 * 例程不同，它需要它，因此我们不能使用 DirectFunctionCall2。
 */

#define ENUM_IS_LEFTMOST(x) ((x) == InvalidOid)

PG_FUNCTION_INFO_V1(gin_enum_cmp);

Datum gin_enum_cmp(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);
	int			fc_res = 0;

	if (ENUM_IS_LEFTMOST(fc_a))
	{
		fc_res = (ENUM_IS_LEFTMOST(fc_b)) ? 0 : -1;
	}
	else if (ENUM_IS_LEFTMOST(fc_b))
	{
		fc_res = 1;
	}
	else
	{
		fc_res = DatumGetInt32(CallerFInfoFunctionCall2(enum_cmp,
													 fcinfo->flinfo,
													 PG_GET_COLLATION(),
													 ObjectIdGetDatum(fc_a),
													 ObjectIdGetDatum(fc_b)));
	}

	PG_RETURN_INT32(fc_res);
}

static Datum fc_leftmostvalue_enum(void)
{
	return ObjectIdGetDatum(InvalidOid);
}

GIN_SUPPORT(anyenum, false, fc_leftmostvalue_enum, gin_enum_cmp)

static Datum fc_leftmostvalue_uuid(void)
{
	/*
	 * palloc0 将创建一个全零的 UUID：
	 * "00000000-0000-0000-0000-000000000000"
	 */
	pg_uuid_t  *fc_retval = (pg_uuid_t *) palloc0(sizeof(pg_uuid_t));

	return UUIDPGetDatum(fc_retval);
}

GIN_SUPPORT(uuid, false, fc_leftmostvalue_uuid, uuid_cmp)

static Datum fc_leftmostvalue_name(void)
{
	NameData   *fc_result = (NameData *) palloc0(NAMEDATALEN);

	return NameGetDatum(fc_result);
}

GIN_SUPPORT(name, false, fc_leftmostvalue_name, btnamecmp)

static Datum fc_leftmostvalue_bool(void)
{
	return BoolGetDatum(false);
}

GIN_SUPPORT(bool, false, fc_leftmostvalue_bool, btboolcmp)
